﻿using System;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reflection;
using System.Threading;
using System.Runtime.InteropServices;
using System.Web;
using System.Web.Hosting;

using WebServer.HTDOCS;
using WebServer.Modules;

namespace WebServer
{
	[Flags]
	public enum CoreState : byte
	{
		/// <summary>
		/// Core is Initializing.
		/// </summary>
		Initializing = 0x00,
		/// <summary>
		/// Cleanup Operation is currently in progress.
		/// </summary>
		Cleaning = 0x01,
		/// <summary>
		/// Core has crashed for some overrated and/or stupid reason.
		/// </summary>
		Crashed = 0x02,
		/// <summary>
		/// Core is started and currently running.
		/// </summary>
		Started = 0x04,
		/// <summary>
		/// Core has been stopped.
		/// </summary>
		Stopped = 0x08,
		/// <summary>
		/// Core is exiting/closing/saying good bye.
		/// </summary>
		Closing = 0x10,
		/// <summary>
		/// Core is restarting, flagged as Closing + Stopped.
		/// </summary>
		Restarting = Closing | Stopped,
	}

	/// <summary>
	/// The main WebServer Core, which handles everything from web requests to webObj compilation and client management and more
	/// </summary>
	public class Core
	{
		private static CoreState _State;
		/// <summary>
		/// Gets a value indecating the current Core State flags.
		/// </summary>
		public static CoreState State { get { return _State; } }

		/// <summary>
		/// Gets a value indecating whether the Core State contains the given state.
		/// </summary>
		/// <param name="state">State to check.</param>
		/// <returns>True if Core State contains the given state, otherwise; false.</returns>
		public static bool CheckState(CoreState state) { return ((_State & state) != 0); }

		/// <summary>
		/// Updates the current Core State.
		/// </summary>
		/// <param name="state">CoreState to set.</param>
		public static void UpdateState(CoreState state)
		{
			_State = state;
			ConsoleWriteLine("State: {0}", _State);
		}

		private enum CtrlTypes { CTRL_C_EVENT = 0, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT = 5, CTRL_SHUTDOWN_EVENT }

		[DllImport("Kernel32.dll")]
		private static extern bool SetConsoleCtrlHandler(CTRLEventHandler Handler, bool Add);
		private delegate bool CTRLEventHandler(CtrlTypes CtrlType);
		private static event CTRLEventHandler CTRL_Event;

		private static bool Core_CTRL_Event(CtrlTypes ctrlType)
		{
			switch (ctrlType)
			{
				case CtrlTypes.CTRL_CLOSE_EVENT: { Close(); } break;
				case CtrlTypes.CTRL_LOGOFF_EVENT: { Close(); } break;
				case CtrlTypes.CTRL_SHUTDOWN_EVENT: { Close(); } break;
				default: { } break;
			}
			return false;
		}

		private static string _BaseDirectory = IOUtility.GetBaseDirectory();
		/// <summary>
		/// Gets the base directory path, relative to the Core assembly location.
		/// </summary>
		public static string BaseDirectory { get { return IOUtility.GetSafeDirectoryPath(_BaseDirectory); } }

		private static KeyValuePair<Thread, Cleanup> _CleanupThread = new KeyValuePair<Thread, Cleanup>();
		private static Dictionary<Thread, Worker> _WorkerThreads = new Dictionary<Thread, Worker>();

		private static Thread _Thread;
		/// <summary>
		/// Core Thread
		/// </summary>
		public static Thread Thread { get { return _Thread; } }

		private static HttpListener _HTTPListener;
		/// <summary>
		/// Core HttpListener
		/// </summary>
		public static HttpListener HTTPListener { get { return GetListener(true, Listener.Hosts); } }

		private static Dictionary<string, Client> _Clients = new Dictionary<string, Client>();
		/// <summary>
		/// Client Connection registry, stores all clients that are currently connected to the WebServer, or have been active recently
		/// </summary>
		public static Dictionary<string, Client> Clients { get { return _Clients; } }

		private static volatile List<Client> _Flooders = new List<Client>();

		private static Assembly _CustomAssembly;
		/// <summary>
		/// Gets the custom assembly for all compiled custom files.
		/// </summary>
		public static Assembly CustomAssembly { get { return _CustomAssembly; } }

		private static void CompileCustomFiles()
		{
			CSharpCompiler csc = new CSharpCompiler("CustomFiles.dll");

			csc.AddReference("WebServer.dll");
			csc.AddReference("AspNetHost.dll");
			csc.AddFileMask("*.ws.cs");
			csc.AddFileMask("*.wsobj.cs");
			csc.AddFileMask("*.wsmod.cs");

			Core.ConsoleWriteLine("Compiling custom C# files...");

			csc.Compile();

			Core.ConsoleWriteLine(csc.StatusMessage);

			if (csc.Errors.Length > 0)
			{
				for (int i = 0; i < csc.Errors.Length; i++)
					Core.ConsoleWriteError(csc.Errors[i]);

				Core.WaitForKeyPress("Press any key to continue.");
			}
			else if (csc.Results != null)
			{
				_CustomAssembly = csc.Results.CompiledAssembly;
			}
		}

		private static void Events_CoreMessage(CoreMessageEventArgs e)
		{
			if (e.Handled)
				return;

			switch (e.Action)
			{
				case CoreMessageAction.Output: { e.String = String.Format("[{0}]: {1}", Config.SystemName, e.String); } break;
				case CoreMessageAction.Error: { e.String = String.Format("[{0}]: {1}", Config.SystemName, e.String); } break;
			}
		}

		/// <summary>
		/// Initializes the entire System, this should be called before anything else in the system to ensure that the system initializes correctly. Alternatively, Core.Start wraps this method
		/// </summary>
		public static void Initialize()
		{
			try
			{
				Events.CoreMessage += new CoreMessageHandler(Events_CoreMessage);

				CTRL_Event += new CTRLEventHandler(Core_CTRL_Event);

				try { SetConsoleCtrlHandler(CTRL_Event, true); }
				catch { }

				Version ver = Assembly.GetAssembly(typeof(Core)).GetName().Version;
				string verDesc = String.Format("Version {1}.{2}.{3} build {4}", Config.SystemName, ver.Major, ver.Minor, ver.Revision, ver.Build);

				ConsoleWriteLine(verDesc);

				string sep = String.Empty;
				for (int i = 0; i < verDesc.Length; i++)
				{ sep += "="; }

				ConsoleWriteLine(sep);

				UpdateState(CoreState.Initializing);

				if (!Config.Enabled)
				{
					ConsoleWriteLine("An attempt was made to start the {0} Core, but could not be completed because the system is Disabled in the configuration options.", Config.SystemName);

					if (Config.StandAloneMode)
					{
						WaitForKeyPress("Press any key to continue.");
					}

					UpdateState(CoreState.Stopped);
					return;
				}

				IOUtility.EnsureDirectory(Core.BaseDirectory + "\\" + Config.SystemName + "\\HTDOCS\\");

				ConsoleWriteLine("Starting Core Thread...");

				_Thread = new Thread(InitializeCore);
				_Thread.Start();
				_Thread.Name = Config.SystemName + " Core";
			}
			catch
			{
				Core.Restart();
			}
		}

		#region Core Operation Handlers
		/// <summary>
		/// Starts the WebServer Core
		/// </summary>
		public static void Start()
		{
			if (CheckState(CoreState.Started))
				return;

			Initialize();
		}

		/// <summary>
		/// Closes the WebServer Core
		/// </summary>
		public static void Close()
		{
			if (CheckState(CoreState.Closing))
				return;

			Events.InvokeCoreClosed();

			UpdateState(CoreState.Closing);
		}

		private delegate void RestartCoreHandler();
		/// <summary>
		/// Handles the Core restart.
		/// </summary>
		private static event RestartCoreHandler RestartEvent = new RestartCoreHandler(PerformRestart);

		/// <summary>
		/// Performs a restart on the WebServer Core.
		/// </summary>
		private static void PerformRestart()
		{
			Close();

			UpdateState(CoreState.Restarting);

			Console.Clear();

			Start();
		}

		/// <summary>
		/// Restarts the WebServer Core
		/// </summary>
		public static void Restart()
		{
			RestartEvent.Invoke();
		}
		#endregion

		/// <summary>
		/// Initializes and configures a new HttpListener instance to handle web requests.
		/// </summary>
		/// <param name="start">If true, will automatically start the new HttpListener instance when initialized.</param>
		/// <param name="prefixes">List of Hosts to listen on.</param>
		/// <returns>HttpListener instance.</returns>
		private static HttpListener GetListener(bool start, List<string> prefixes)
		{
			try
			{
				if (_HTTPListener == null)
					_HTTPListener = new HttpListener();

				if (start)
				{
					while (!_HTTPListener.IsListening)
					{
						if (prefixes == null || prefixes.Count == 0)
						{
							ConsoleWriteError("Inactive: No remote hosts specified.");
							UpdateState(CoreState.Stopped);
							return null;
						}

						for (int i = 0; i < prefixes.Count; i++)
						{
							_HTTPListener.Prefixes.Add(prefixes[i]);
						}

						_HTTPListener.Start();
					}
				}
			}
			catch (Exception ex)
			{
				if (ex is HttpListenerException)
				{
					ConsoleWriteError("The {0} service could not start because there seems to be a service listening on the specified hosts.", Config.SystemName);
					return null;
				}
				else
				{
					ConsoleWriteError("Exception Thrown:\n{0}", ex);
					return null;
				}
			}

			return _HTTPListener;
		}

		/// <summary>
		/// Initializes the main core thread.
		/// </summary>
		[STAThread]
		private static void InitializeCore()
		{
			try
			{
				if (!Config.Enabled)
				{
					UpdateState(CoreState.Stopped);

					ConsoleWriteError("An attempt was made to start the {0} Core, but could not be completed because the system is Disabled in the configuration options.", Config.SystemName);
					WaitForKeyPress("Press any key to continue.");
				}
				else
				{
					UpdateState(CoreState.Started);

					CompileCustomFiles();

					CoreModules.Initialize();

					RequestHandler.AddIndex("index.html");

					_Clients = new Dictionary<string, Client>();
					_HTTPListener = GetListener(true, Listener.Hosts);

					if (_HTTPListener == null)
					{
						ConsoleWriteError("The listener could not be initialized.");
						WaitForKeyPress("Press any key to continue.");
					}
					else
					{
						if (_HTTPListener.IsListening)
						{
							foreach (string host in _HTTPListener.Prefixes)
							{ ConsoleWriteLine("Listening on: {0}", host); }
						}

						Events.InvokeCoreInitialized();

						try
						{
							while
								(!CheckState(CoreState.Closing) && !CheckState(CoreState.Stopped) &&
								!CheckState(CoreState.Restarting) && !CheckState(CoreState.Crashed))
							{
								if (_HTTPListener != null)
								{
									HttpListenerContext ctx = _HTTPListener.GetContext();

									if (ctx != null)
									{
										Worker worker = new Worker(ctx);
										Thread thread = new Thread(worker.ProcessRequest);

										thread.Start();

										_WorkerThreads.Add(thread, worker);
									}
								}
							}
						}
						catch (Exception ex)
						{
							UpdateState(CoreState.Crashed);
							ConsoleWriteError(true, ex.ToString());
						}

						UpdateState(CoreState.Closing);

						//try { _CoreThread.Join(); }
						//catch { Environment.Exit(5); }
					}
				}
			}
			catch (Exception ex)
			{
				UpdateState(CoreState.Crashed);
				ConsoleWriteError(true, ex.ToString());

				Core.Restart();
			}
		}

		public static void TryCleanup(bool forceFull)
		{
			if (!CheckState(CoreState.Cleaning) && Cleanup.LastCleanup.Add(Config.CleanupInterval) <= DateTime.Now)
			{
				if (_CleanupThread.Key != null)
					_CleanupThread.Key.Join();

				Cleanup cleanup = new Cleanup(forceFull);
				Thread thread = new Thread(cleanup.Initialize);

				thread.Start();

				_CleanupThread = new KeyValuePair<Thread, Cleanup>(thread, cleanup);
			}
		}

		#region Console Read/Write Functions
		public static ConsoleKeyInfo WaitForKeyPress(string message)
		{
			ConsoleKeyInfo key = new ConsoleKeyInfo();

			if (Config.StandAloneMode)
			{
				Console.WriteLine(message);
				Console.WriteLine("");

				while ((key = ConsoleReadKey()) == null)
				{ }
			}

			return key;
		}

		/// <summary>
		/// Writes to the Console window
		/// </summary>
		/// <param name="text">Text to write</param>
		public static void ConsoleWrite(string text)
		{
			ConsoleWrite(false, text);
		}

		/// <summary>
		/// Writes a line to the Console window
		/// </summary>
		/// <param name="ignore">Ignores the Config.ConsoleMessages option to always allow writing</param>
		/// <param name="text">Text to write</param>
		public static void ConsoleWrite(bool ignore, string text)
		{
			CoreMessageEventArgs e = new CoreMessageEventArgs(CoreMessageAction.Output, text);
			Events.InvokeCoreMessage(e);

			if (ignore || Config.ConsoleMessages)
			{
				if (!e.Handled)
				{
					Console.Write(e.Message);
				}
			}
		}

		/// <summary>
		/// Writes a formatted message to the Console window
		/// </summary>
		/// <param name="format">String format</param>
		/// <param name="args">Format arguments</param>
		public static void ConsoleWrite(string format, params object[] args)
		{
			ConsoleWrite(false, format, args);
		}

		/// <summary>
		/// Writes a formatted message to the Console window
		/// </summary>
		/// <param name="ignore">Ignores the Config.ConsoleMessages option to always allow writing</param>
		/// <param name="format">String format</param>
		/// <param name="args">Format arguments</param>
		public static void ConsoleWrite(bool ignore, string format, params object[] args)
		{
			CoreMessageEventArgs e = new CoreMessageEventArgs(CoreMessageAction.Output, format, args);
			Events.InvokeCoreMessage(e);

			if (ignore || Config.ConsoleMessages)
			{
				if (!e.Handled)
				{
					Console.Write(e.Message);
				}
			}
		}

		/// <summary>
		/// Writes a line to the Console window
		/// </summary>
		/// <param name="text">Text to write</param>
		public static void ConsoleWriteLine(string text)
		{
			ConsoleWriteLine(false, text);
		}

		/// <summary>
		/// Writes a line to the Console window
		/// </summary>
		/// <param name="ignore">Ignores the Config.ConsoleMessages option to always allow writing</param>
		/// <param name="text">Text to write</param>
		public static void ConsoleWriteLine(bool ignore, string text)
		{
			CoreMessageEventArgs e = new CoreMessageEventArgs(CoreMessageAction.Output, text);
			Events.InvokeCoreMessage(e);

			if (ignore || Config.ConsoleMessages)
			{
				if (!e.Handled)
				{
					Console.WriteLine(e.Message);
				}
			}
		}

		/// <summary>
		/// Writes a formatted line to the Console window
		/// </summary>
		/// <param name="format">String format</param>
		/// <param name="args">Format arguments</param>
		public static void ConsoleWriteLine(string format, params object[] args)
		{
			ConsoleWriteLine(false, format, args);
		}

		/// <summary>
		/// Writes a formatted line to the Console window.
		/// </summary>
		/// <param name="ignore">Ignores the Config.ConsoleMessages option to always allow writing</param>
		/// <param name="format">String format</param>
		/// <param name="args">Format arguments</param>
		public static void ConsoleWriteLine(bool ignore, string format, params object[] args)
		{
			CoreMessageEventArgs e = new CoreMessageEventArgs(CoreMessageAction.Output, format, args);
			Events.InvokeCoreMessage(e);

			if (ignore || Config.ConsoleMessages)
			{

				if (!e.Handled)
				{
					Console.WriteLine(e.Message);
				}
			}
		}

		/// <summary>
		/// Writes an error to the Console window
		/// </summary>
		/// <param name="error">Error message to write</param>
		public static void ConsoleWriteError(string error)
		{
			ConsoleWriteError(false, error);
		}

		/// <summary>
		/// Writes an error to the Console window
		/// </summary>
		/// <param name="ignore">Ignores the Config.ConsoleMessages option to always allow writing.</param>
		/// <param name="error">Error message to write.</param>
		public static void ConsoleWriteError(bool ignore, string error)
		{
			CoreMessageEventArgs e = new CoreMessageEventArgs(CoreMessageAction.Error, error);
			Events.InvokeCoreMessage(e);

			if (ignore || Config.ConsoleMessages)
			{
				if (!e.Handled)
				{
					Console.WriteLine(e.Message);
					Logging.WriteError(e.Message);
				}
			}
		}

		/// <summary>
		/// Writes a formatted error to the Console window.
		/// </summary>
		/// <param name="format">String format.</param>
		/// <param name="args">Format arguments.</param>
		public static void ConsoleWriteError(string format, params object[] args)
		{
			ConsoleWriteError(false, format, args);
		}

		/// <summary>
		/// Writes a formatted error to the Console window.
		/// </summary>
		/// <param name="ignore">Ignores the Config.ConsoleMessages option to always allow writing.</param>
		/// <param name="format">String format.</param>
		/// <param name="args">Format arguments.</param>
		public static void ConsoleWriteError(bool ignore, string format, params object[] args)
		{
			CoreMessageEventArgs e = new CoreMessageEventArgs(CoreMessageAction.Error, format, args);
			Events.InvokeCoreMessage(e);

			if (ignore || Config.ConsoleMessages)
			{
				if (!e.Handled)
				{
					Console.WriteLine(e.Message);
					Logging.WriteError(e.Message);
				}
			}
		}

		/// <summary>
		/// Reads the next character from the standard input stream.
		/// </summary>
		/// <returns>The int32 variable for the equivilent character.</returns>
		public static int ConsoleRead()
		{
			return Console.Read();
		}

		/// <summary>
		/// Obtains the next character or function pressed by the user. The pressed key is displayed in the console window.
		/// </summary>
		/// <returns>The key information for the key pressed.</returns>
		public static ConsoleKeyInfo ConsoleReadKey()
		{
			return Console.ReadKey();
		}

		/// <summary>
		/// Obtains the next character or function pressed by the user. The pressed key is optionally displayed in the console window.
		/// </summary>
		/// <param name="intercept">Determines whether to display the pressed key in the console window. True to not display the pressed key; otherwise, false.</param>
		/// <returns>The key information for the key pressed.</returns>
		public static ConsoleKeyInfo ConsoleReadKey(bool intercept)
		{
			return Console.ReadKey(intercept);
		}

		/// <summary>
		/// Reads the next line of characters from the standard input stream.
		/// </summary>
		/// <returns>The string representation of the line read from the standard input stream.</returns>
		public static string ConsoleReadLine()
		{
			string line = Console.ReadLine();

			CoreMessageEventArgs e = new CoreMessageEventArgs(CoreMessageAction.Input, line);
			Events.InvokeCoreMessage(e);

			if (e.Handled)
				return e.Message;
			else
				e.Handled = true;

			return line;
		}
		#endregion

		/// <summary>
		/// Provides methods for processing incoming web requests.
		/// </summary>
		public sealed class Worker
		{
			private bool _Completed = false;
			/// <summary>
			/// Gets a value indicating if this worker has done all it can.
			/// </summary>
			public bool Completed { get { return _Completed; } }

			private RequestStatus _Status = RequestStatus.Processing;
			/// <summary>
			/// Gets the status of this request instance.
			/// </summary>
			public RequestStatus Status { get { return _Status; } }

			private HttpListenerContext _Context;

			public Worker(HttpListenerContext context)
			{
				_Context = context;
			}

			public void ProcessRequest()
			{
				try
				{
					if (!CheckState(CoreState.Started))
					{
						_Status = RequestStatus.Failed;
						_Completed = true;
						return;
					}

					string
						msg = _Context.Request.Url.ToString(),
						searchKey = _Context.Request.RemoteEndPoint.Address.ToString();

					Client client = null;

					if (Firewall.Contains(_Context.Request.RemoteEndPoint.Address))
					{
						ConsoleWriteError("Client Connection rejected: '{0}' is firewalled.", searchKey);
						_Status = RequestStatus.Failed;
						_Completed = true;
						return;
					}

					if (!_Clients.ContainsKey(searchKey))
					{
						if (Config.MaxConnections != 0 && _Clients.Count > Config.MaxConnections)
						{
							ConsoleWriteError("Client Connection rejected: Maximum connections of '{0}' exceeded.", Config.MaxConnections);
							_Status = RequestStatus.Failed;
							_Completed = true;
							return;
						}

						client = new Client(_Context.Request.RemoteEndPoint);

						ConsoleWriteLine("[{0}] Client connected.", client.ToString());
						Logging.WriteLine(client, "Connected.");

						client.OnRequest(_Context);

						_Clients.Add(searchKey, client);

						client.OnRequest(_Context);
						HandleRequest(msg, client);
					}
					else
					{
						client = (Client)_Clients[searchKey];

						if (client.Flooding)
						{
							if (!_Flooders.Contains(client))
							{
								ConsoleWriteError("[{0}] Flood attempt detected.", searchKey);
								Logging.WriteLine(client, "Flood attempt detected.");

								msg = String.Format("Flood attempt detected.");

								client.OnRequest(_Context);
								_Status = RequestStatus.Failed;

								_Flooders.Add(client);
							}
							else
							{
								client.OnRequest(_Context);
								_Status = RequestStatus.Failed;
							}
						}
						else
						{
							if (_Flooders.Contains(client))
								_Flooders.Remove(client);

							client.OnRequest(_Context);
							HandleRequest(msg, client);
						}
					}

					HTTPRequestEventArgs e = new HTTPRequestEventArgs(_Context, client, _Status);
					Events.InvokeHTTPRequest(e);
				}
				catch { }//(Exception ex) { ConsoleWriteError(true, ex.ToString()); }
				finally { }

				try
				{
					_Context.Response.OutputStream.Flush();
					_Context.Response.OutputStream.Close();
					_Context.Response.OutputStream.Dispose();
				}
				catch { }//(Exception ex) { ConsoleWriteError(true, ex.ToString()); }

				Core.TryCleanup(false);
				_Completed = true;
			}

			private void HandleRequest(string msg, Client client)
			{
				_Status = RequestStatus.Processing;

				Core.ConsoleWriteLine("[" + client.ToString() + "] Request: " + _Context.Request.RawUrl);
				Logging.WriteLine(client, "Request: " + _Context.Request.RawUrl);

				RequestHandler rh = new RequestHandler(msg, client, _Context);

				rh.Process();

				if (rh.WebObject != null)
				{
					_Status = RequestStatus.Successful;

					WebObjectRetrievedEventArgs e = new WebObjectRetrievedEventArgs(rh.Context.Request.Url, rh.Client, rh.WebObject, rh.Message);
					Events.InvokeWebObjectRetrieved(e);

					Core.ConsoleWriteLine("[" + client.ToString() + "] Request Reply: " + rh.WebObject.Name);
					Logging.WriteLine(client, "Request Reply: " + rh.WebObject.Name);
				}
				else
				{
					_Status = RequestStatus.Failed;

					if (!String.IsNullOrEmpty(rh.Message))
					{
						Core.ConsoleWriteLine("[" + client.ToString() + "] " + rh.Message);
						Logging.WriteLine(client, rh.Message);
					}
				}
			}
		}

		/// <summary>
		/// Provides a clean-up service that also utilizes garbage collection to reclaim unused, allocated memory.
		/// </summary>
		internal class Cleanup
		{
			private static DateTime _LastCleanup = DateTime.Now;
			/// <summary>
			/// Gets the time when the last cleanup operation was performed.
			/// </summary>
			public static DateTime LastCleanup { get { return _LastCleanup; } }

			private bool _Completed = false;
			/// <summary>
			/// Gets a value indicating if this cleanup operation has done all it can.
			/// </summary>
			public bool Completed { get { return _Completed; } }

			private bool _ForceFullCleanup = false;
			/// <summary>
			/// Gets a value indicating if this cleanup operation should force a full cleanup.
			/// This will force all worker threads to abort and will disconnect all Clients.
			/// </summary>
			public bool ForceFullCleanup { get { return _ForceFullCleanup; } }

			/// <summary>
			/// Creates an instance of Cleanup.
			/// </summary>
			/// <param name="forceFullCleanup">Determines whether the Cleanup operation should force a full cleanup. This will force all worker threads to abort and will disconnect all Clients.</param>
			public Cleanup(bool forceFullCleanup)
			{
				_ForceFullCleanup = forceFullCleanup;
			}

			public void Initialize()
			{
				CoreState oldState = _State;

				try
				{
					if (!_ForceFullCleanup)
					{
						if (CheckState(CoreState.Cleaning))
						{
							_Completed = true;
							return;
						}
					}

					ConsoleWriteLine("Cleanup initialized.");

					UpdateState(_State | CoreState.Cleaning);

					ConsoleWriteLine("Cleaning worker threads...");

					Dictionary<Thread, Worker> threads = new Dictionary<Thread, Worker>(_WorkerThreads);
					List<Thread> threadsToClean = new List<Thread>();

					foreach (KeyValuePair<Thread, Worker> kvp in threads)
					{
						Thread thread = kvp.Key;
						Worker worker = kvp.Value;

						if (thread == null || worker == null)
							continue;

						if (_ForceFullCleanup)
						{
							threadsToClean.Add(thread);
						}
						else if (worker.Completed)
						{
							threadsToClean.Add(thread);
						}
					}

					foreach (Thread thread in threadsToClean)
					{
						if (thread == null)
							continue;

						thread.Join();

						if (_WorkerThreads.ContainsKey(thread))
							_WorkerThreads.Remove(thread);
					}

					ConsoleWriteLine("Cleaned {0} worker threads.", threadsToClean.Count);

					threadsToClean.Clear();

					ConsoleWriteLine("Cleaning clients...");

					Dictionary<string, Client> clients = new Dictionary<string, Client>(_Clients);
					List<string> clientsToRemove = new List<string>();

					foreach (KeyValuePair<string, Client> kvp in clients)
					{
						string conName = kvp.Key;
						Client con = kvp.Value;

						if (_ForceFullCleanup)
						{
							con.WebObjects.Clear();
							clientsToRemove.Add(conName);
						}
						else if (con.AFK)
						{
							con.WebObjects.Clear();
							clientsToRemove.Add(conName);
						}

						if (con.WebObjects.Count >= con.RequestLimit)
							con.WebObjects.Clear();
					}

					foreach (string key in clientsToRemove)
					{
						if (_Clients.ContainsKey(key))
							_Clients.Remove(key);
					}

					ConsoleWriteLine("Cleaned {0} clients.", clientsToRemove.Count);
					clientsToRemove.Clear();

					ConsoleWriteLine("Initializing garbage collection...");

					long memoryUsageBefore = GC.GetTotalMemory(false);
					long memoryUsageAfter = GC.GetTotalMemory(true);
					long freedMemory = memoryUsageBefore - memoryUsageAfter;

					ConsoleWriteLine("Reclaimed {0} total memory.", FormatByteAmount(freedMemory));
				}
				catch (Exception ex) { ConsoleWriteError(true, ex.ToString()); }
				finally { }

				UpdateState(oldState);
				_LastCleanup = DateTime.Now;
				_Completed = true;
			}

			public static string FormatByteAmount(long totalBytes)
			{
				if (totalBytes > 1000000000)
					return String.Format("{0:F1} GB", (double)totalBytes / 1073741824);

				if (totalBytes > 1000000)
					return String.Format("{0:F1} MB", (double)totalBytes / 1048576);

				if (totalBytes > 1000)
					return String.Format("{0:F1} KB", (double)totalBytes / 1024);

				return String.Format("{0} Bytes", totalBytes);
			}
		}
	}
}